package com.yc.springmvc;

import com.google.gson.Gson;
import com.yc.springmvc.controller.IndexController;
import com.yc.springmvc.controller.MyIndexAction;
import com.yc.springmvc.controller.ParamDo;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.logging.Handler;

public class MyDispatchServlet extends HttpServlet {

    @Override
    public void init(ServletConfig config) throws ServletException{
        super.init(config);
    }


    Map<String,Object> ioc = new HashMap<>();
    {
        ioc.put("cotroller", new IndexController());
        ioc.put("action", new MyIndexAction());
        ioc.put("do", new ParamDo());
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.service(req, resp);

        Handler handler = handlerMapping(req);
        if(handler==null){
            // 404 错误
            defaultServletHandler(req,resp);
            return;
        }
        Object res = handlerAdapter(handler, req, resp);
        System.out.println(" ***************************    res = " + res);

        // 1. 405
        // 2. 对 res 对象的返回 => 1) json, 2) 页面跳转
        if(returnJson(handler)){   //如果returnJson(handler)返回true，则表示需要返回 JSON 数据
            // json
            Gson gson = new Gson();
            String json = gson.toJson(res);
            resp.setContentType("application/json;charset=utf-8");
            // 获取 HttpServletResponse 的 Writer 对象，然后将 JSON 数据写入其中。这样可以将 JSON 数据写入 HTTP 响应的主体部分。
            resp.getWriter().append(json);
            //刷新 HttpServletResponse 的缓冲区，确保所有数据都被写入响应。这样可以确保 JSON 数据被发送到客户端。
            resp.flushBuffer();
        } else if(returnHtml(handler)){
            // html
            String path = null;
            /*
            * 根据返回结果res的类型判断跳转路径path，
            * 如果是ModelAndView类型，则取其viewName作为路径；
            * 如果是String类型，则直接作为路径。
            * */
            if(res instanceof ModelAndView){
                path = ((ModelAndView) res).getViewName();
            } else if(res instanceof String){
                path = (String) res;
            }
            // 只处理 响应重定向
            //从字符串path中去除前缀"redirect:"，并将剩余部分作为新的字符串url。
            String url = path.substring("redirect:".length());
            resp.sendRedirect(url);
            resp.flushBuffer();
        } else {
            // TODO 其他可能返回结果
        }

    }



    //静态资源处理
    private void defaultServletHandler(HttpServletRequest req, HttpServletResponse resp) {
        String path = req.getServletPath();
        // /A.html
        //getResource("")方法会返回一个URL对象，指向指定路径下的资源。在这里，传入空字符串""表示获取当前类加载器的根路径    通常用于获取类加载器加载的资源文件的路径。
        URL url = this.getClass().getClassLoader().getResource("");// 获取当前类的类加载器的资源路径。   getClassLoader()方法返回的就是加载该类的类加载器
        System.out.println("url.getPath() = " + url.getPath());
        // URL 地址解码
        String filePath = url.getPath();
        filePath = URLDecoder.decode(filePath);//对URL地址进行解码。 表示对URL地址进行解码，将编码后的URL字符串还原为原始的URL路径或参数。
        System.out.println("filePath = " + filePath);
        // 拼接文件路径
        File dir = new File(filePath.substring(1));//filePath.substring(1)是对解码后的路径filePath进行截取操作，从索引1开始截取到结尾
        File webRoot = dir.getParentFile().getParentFile();//dir.getParentFile() 返回表示当前文件或目录的父目录的File对象。  **********获取Web应用的根目录。
        //如果path是相对路径，则会在webRoot目录下寻找对应的文件或目录；如果path是绝对路径，则会根据绝对路径构建文件对象。
        File file = new File(webRoot, path);//根据请求路径构建要返回的文件对象。  webRoot是Web应用的根目录，path是请求路径。
        System.out.println("file = " + file);
        if(path.endsWith(".html")){   //用于检查字符串是否以指定的后缀结尾。
            resp.setContentType("text/html;charset=utf-8");
            try(FileInputStream fis = new FileInputStream(file);//用于读取文件内容，
                OutputStream out = resp.getOutputStream()){//用于获取响应的输出流。
                byte[] bytes = new byte[1024];
                int count;
                while((count=fis.read(bytes))>0){ //循环读取文件内容，每次最多读取1024字节，并将实际读取的字节数存储在count中。当fis.read(bytes)返回-1时表示已读取完整个文件。
                    out.write(bytes,0,count);  //将读取的字节数组bytes中的内容写入到输出流out中，写入长度为count。
                }
            } catch (IOException e) {
                throw new RuntimeException("静态资源文件输出错误!",e);
            }
        } else if(path.endsWith(".css")){
            // css
        } else if(path.endsWith(".js")){
            // js
        } else {
            // 其他文件类型
        }
    }




    // 获取处理器(控制器+方法)
    public Handler handlerMapping(HttpServletRequest req){
        String path = req.getServletPath();   //获取请求的Servlet路径。
        // 查找控制器对象
        for (Object controller : ioc.values()) {
            // 获取类级别的 requestmapping 注解  =》  用于进行类 的 注解 的 requestmapping注解
            RequestMapping requestMapping =
                    controller.getClass().getAnnotation(RequestMapping.class);  //底层String[] 数组  存储数据
            String annoPath = null;
            if(requestMapping!=null){
                annoPath = requestMapping.value().length>0 ?  //@AliasFor("path"): 该注解表示当前注解的别名是path，即value和path可以互相代替使用
                        requestMapping.value()[0]:
                        requestMapping.path()[0];     // requestMapping默认将地址存储在value里，但即value和path可以互相代替使用，总之 获取出地址
                if (annoPath.startsWith("/")==false) {  //即判断annoPath是否不是以"/"开头。
                    // 补 /
                    annoPath = "/" + annoPath;
                }
            }
            for (Method method : controller.getClass().getDeclaredMethods()) {   //getDeclaredMethods()这个方法获取的是类中声明的所有方法，
                                                                                // 而不是包括从父类继承而来的方法。以Method数组的形式返回
                // 定义一个新的的变量, 记录当前方法的地址
                String controllerPath = annoPath == null? "": annoPath;
                RequestMapping requestMapping1 =
                        method.getAnnotation(RequestMapping.class);// RequestMapping.class 是个注解类  getAnnotation 获取有某个注解类的方法

                // GetMapping PostMapping...
                if(requestMapping1 == null){
                    continue;
                }
                String methodPath = requestMapping1.value().length>0 ?
                        requestMapping1.value()[0]:
                        requestMapping1.path()[0];
                if (methodPath.startsWith("/")==false) {
                    // 补 /
                    methodPath = "/" + methodPath;
                }
                controllerPath += methodPath;
                if(path.equals(controllerPath)){

                    System.out.println("--------------- path = " + path);
                    System.out.println("--------------- method = " + method);
                    return new Handler(controller, method);
                }
            }

        }
        return null;
    }

    // 执行处理器
    public Object handlerAdapter(Handler handler,
                                 HttpServletRequest req,
                                 HttpServletResponse resp){
        // 解析参数
//        Parameter[] parameters = handler.getMethod().getParameters();
//        for (Parameter parameter : parameters) {
//            System.out.println("=========== parameter = " + parameter.getName());
//        }
        List<String> names = getParameterNames(handler.getMethod());  //获取method数组中对应多个方法 的方法名集合
        // 方法参数值数组
        List<Object> values = new ArrayList<>();
        //getParameters()返回一个Parameter数组，其中包含了方法的  ** 所有参数信息  **。这个方法可以用来获取方法的参数列表，包括参数的类型、名称等信息。
        for (int i = 0; i < handler.getMethod().getParameters().length; i++) {
            String name = names.get(i);
            String svalue = req.getParameter(name);  //返回一个字符串，表示请求参数name对应的值。如果请求中不存在名为name的参数，则返回null。
            //getParameters() 获取方法中  参数
            Parameter parameter = handler.getMethod().getParameters()[i];
            // 转型
            if(parameter.getType().equals(String.class)){
                values.add(svalue);  // 字符串
            } else if(parameter.getType().equals(Integer.class) ||
                    parameter.getType().equals(int.class)){
                values.add(Integer.valueOf(svalue));  // int
            } else if(parameter.getType().equals(Long.class) ||
                    parameter.getType().equals(long.class)){
                // TODO long
            } else if(parameter.getType().isAssignableFrom(HttpServletRequest.class)){ //isAssignableFrom是Class类的方法，用于判断当前类是否可以赋值给指定的类。
                values.add(req);
            } else if(parameter.getType().isAssignableFrom(HttpServletResponse.class)){
                values.add(resp);
            } else if(parameter.getType().isAssignableFrom(HttpSession.class)){
                values.add(req.getSession());
            } else {
                if (parameter.getAnnotation(RequestBody.class)!=null) {
                    // 参数类型
                    Class<?> type = parameter.getType();
                    // body内容
                    try {
                        BufferedReader reader = req.getReader();
                        char[] chars = new char[1024];
                        int count;
                        StringBuffer sb = new StringBuffer();
                        while((count=reader.read(chars))>0){
                            sb.append(chars, 0, count);
                        }
                        String json = sb.toString();
                        System.out.println("json = " + json);
                        Gson gson = new Gson();
                        //将 JSON 格式的字符串 json 解析成指定类型 type 的 Java 对象，并将解析后的对象赋值给变量 object。
                        Object object = gson.fromJson(json, type);
                        System.out.println("object = " + object);
                        values.add(object);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else {
                    // 实体对象
                    Object object = createObject(parameter, req);
                    values.add(object);
                }
            }
        }
        handler.getMethod().setAccessible(true);   //通过调用setAccessible(true)方法，取消了对该方法的访问权限限制。
                                                    // 这样做可以使得即使方法是私有的，也可以通过反射进行访问和调用。
        try {
            return handler.getMethod().invoke(handler.getController(), values.toArray());  //invoke方法可以在指定对象上调用指定方法，并传递参数
            //handler.getController()  获取handler对象中的控制器对象。   values.toArray(): 将values列表转换为数组。 Object[] toArray();
        } catch (Exception e) {
            throw new RuntimeException("控制器方法执行错误!", e);
        }
    }

    private Object createObject(Parameter parameter, HttpServletRequest req) {
        Object obj = null;
        try {
            obj = parameter.getType().newInstance();//newInstance()方法是Class类的方法，用于创建该类的新实例。
            setFiledValue(obj, req, null);
        } catch (Exception e) {
            throw new RuntimeException("自动装箱错误!", e);
        }
        return obj;
    }


    // 判断请求参数中是否包含  fieldPath 开头的参数
    boolean containSubField(HttpServletRequest req, String fieldPath){
        Enumeration<String> names = req.getParameterNames();//通过调用 getParameterNames() 方法，获取 HTTP 请求中所有参数名的枚举对象。
        /* names.hasMoreElements() 用于在遍历参数名枚举对象时，检查是否还有下一个参数名可以被获取。
           如果返回 true，则表示还有参数名未被处理；如果返回 false，则表示所有参数名已经被处理完毕。*/
        while(names.hasMoreElements()){
            String name = names.nextElement();// nextElement() 方法获取下一个参数名。
            String regex = fieldPath+"\\.\\w+";//用于匹配以指定 fieldPath 开头并跟随一个或多个单词字符的字符串
           //System.out.println("--------regex = " + regex + "  name = " + name);
            if(name.matches(regex)){  //用于判断字符串 name 是否匹配指定正则表达式 regex 的方法
                return true;
            }
        }
        return false;
    }


    //用于设置对象属性值的方法，根据请求中的参数值自动装箱为对象的属性值
    void setFiledValue(Object object, HttpServletRequest req, String fieldPath)
            throws IllegalAccessException, InstantiationException {
        // field => name,age...
        for (Field field : object.getClass().getDeclaredFields()) {  //getDeclaredFields()通过调用该方法可以获取一个 Field 对象数组，其中包含了类中声明的所有字段。
            field.setAccessible(true);  // field.setAccessible(true) 方法，可以将字段的可访问性设置为 true，从而绕过访问控制检查，使得我们可以对该字段进行读取或写入操作
            String paramName = field.getName();//获取了当前字段的名称，并将其赋值给字符串变量 paramName。
            if(fieldPath!=null){
                paramName = fieldPath + "." + paramName;
            }
            System.out.println("paramName = " + paramName);
            String value = req.getParameter(paramName);//用于获取 HTTP 请求中指定参数名的参数值。

            if(value == null && containSubField(req,paramName)){
                // 实体对象
                Object subObject = field.getType().newInstance();
                // 递归调用
                setFiledValue(subObject, req, paramName);
                field.set(object, subObject);
                continue;
            }

            if(field.getType().isAssignableFrom(String.class)){
                field.set(object, value);
            } else if(field.getType().isAssignableFrom(Integer.class)||
                    field.getType().isAssignableFrom(int.class)){
                field.set(object, Integer.valueOf(value));
            } else if(field.getType().isAssignableFrom(Boolean.class)||
                    field.getType().isAssignableFrom(boolean.class)){
                field.set(object, Boolean.valueOf(value));
            } else if(field.getType().isAssignableFrom(Long.class)||
                    field.getType().isAssignableFrom(long.class)){
                // TODO long
            } else if(field.getType().isAssignableFrom(List.class)){
                // TODO list
            } else {
                // 其他类型
            }
        }
    }




    public boolean returnJson(Handler handler){
        //使用 @RestController 或 @ResponseBody 定义，返回值将自动转为 JSON 格式字符串，
        //从handler对象中获取控制器对象的@RestController注解，存储在restController变量中。
        RestController restController =
                handler.getController().getClass().getAnnotation(RestController.class);
        Class<?> returnType = handler.getMethod().getReturnType();  // 获取方法的返回类型，存储在returnType变量中。

        Controller controller =
                handler.getController().getClass().getAnnotation(Controller.class); //对象中获取控制器对象的@Controller注解，存储在controller变量中。
        ResponseBody responseBody = handler.getMethod().getAnnotation(ResponseBody.class);

        return restController!=null && ! returnType.isAssignableFrom(ModelAndView.class)
                || controller !=null && responseBody !=null;


             /*restController != null: 如果控制器对象有@RestController注解，
            说明这是一个 RESTful 控制器，通常用于返回 JSON 数据。

            !returnType.isAssignableFrom(ModelAndView.class): 如果返回类型不是ModelAndView，
            通常表示该方法返回的是数据而不是视图，符合返回 JSON 数据的情况。

            controller != null: 如果控制器对象有@Controller注解，说明这是一个普通的控制器，可能用于返回视图。

            responseBody != null: 如果方法有@ResponseBody注解，通常表示该方法会直接返回数据而不是视图，
            符合返回 JSON 数据的情况。*/
    }

    public boolean returnHtml(Handler handler){
        RestController restController =
                handler.getController().getClass().getAnnotation(RestController.class);
        Class<?> returnType = handler.getMethod().getReturnType();

        Controller controller =
                handler.getController().getClass().getAnnotation(Controller.class);
        ResponseBody responseBody = handler.getMethod().getAnnotation(ResponseBody.class);

        return restController!=null && returnType.isAssignableFrom(ModelAndView.class)  //returnType.isAssignableFrom(ModelAndView.class) 返回类型不是ModelAndView
                || controller !=null && responseBody ==null;
    }


    @Data
    @AllArgsConstructor
    class Handler{
        Object controller;
        Method method;
    }

    public List<String> getParameterNames(Method method) {
        LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer =
                new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);  //获取方法的参数名数组。
        List<String> names = new ArrayList<>();
        if (parameterNames != null) {
            for (String param : parameterNames) {
                names.add(param);
            }
        }
        return names;
    }


}
